package hollycrm.com.libuip;


import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.UiThread;
import android.text.TextUtils;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import org.pjsip.pjsua2.AccountConfig;
import org.pjsip.pjsua2.AuthCredInfo;
import org.pjsip.pjsua2.AuthCredInfoVector;
import org.pjsip.pjsua2.CallInfo;
import org.pjsip.pjsua2.CallOpParam;
import org.pjsip.pjsua2.OnRegStateParam;
import org.pjsip.pjsua2.StringVector;
import org.pjsip.pjsua2.pjsip_inv_state;
import org.pjsip.pjsua2.pjsip_status_code;

import java.util.ArrayList;

import hollycrm.com.libuip.http.ConfigAPI;
import hollycrm.com.libuip.http.UIPConfig;
import hollycrm.com.libuip.http.UIPRequest;
import hollycrm.com.libuip.internal.SocketIOClient;
import hollycrm.com.libuip.sip.SipApp;
import hollycrm.com.libuip.sip.UIPAccount;
import hollycrm.com.libuip.sip.UIPBuddy;
import hollycrm.com.libuip.sip.UIPCall;
import hollycrm.com.libuip.socket.SocketClient;
import hollycrm.com.libuip.socket.UIPMessage;
import hollycrm.com.libuip.utils.UIPMessageAdapter;
import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Created by aber on 2017/4/11.
 * using
 */

public final class UIPClient implements Handler.Callback {

    private static final int SERVER_CONFIG_ERROR        =   401;
    private static final int SERVER_CONFIG_FORMAT_ERROR =   402;
    private static final int SERVER_CONFIG_SOCKET_ERROR =   403;
    private static final int SERVER_SIP_REG_ERROR       =   404;
    private static final int SERVER_CONFIG_SUCCEED      =   201;
    private static final int SERVER_MESSAGE             =   202;
    private static final int SERVER_CONNECTED           =   200;
    private static final int SERVER_DISCONNECT          =   400;

    private static final String TAG = UIPClient.class.getCanonicalName();

    public final static int INCOMING_CALL = 1;
    public final static int CALL_STATE = 2;
    public final static int REG_STATE = 3;
    public final static int BUDDY_STATE = 4;
    public final static int CALL_MEDIA_STATE = 5;

    private UIPUserConfig config;
    private SocketClient socketClient;
    private ConfigAPI configAPI;
    private UIPConfig httpConfig;

    private static SipApp sipApp = new SipApp();
    private static AccountConfig accCfg;
    private static UIPAccount account;
    private static UIPCall currentCall;

    private String lastRegStatus = "";

    private ArrayList<UIPListener> listeners;

    private Context context;
    private Gson gson;

    private boolean isConnected = false;

    private Handler handler = new Handler(Looper.getMainLooper(),this);

    private SocketClient.Callback socketCallback = new SocketClient.Callback() {
        @Override
        public void onSend(String message) {
            Log.d("onSend",message);
        }

        @Override
        public void onReceived(String incomingMessage) {
            UIPMessage message = gson.fromJson(incomingMessage,UIPMessage.class);
            handler.obtainMessage(SERVER_MESSAGE,message).sendToTarget();
        }

        @Override
        public void onConnected() {
            handler.obtainMessage(SERVER_CONNECTED).sendToTarget();
        }

        @Override
        public void onConnecting() {

        }

        @Override
        public void onDisconnected() {
            handler.obtainMessage(SERVER_DISCONNECT).sendToTarget();
        }

        @Override
        public void onError(Throwable e) {
            handler.obtainMessage(SERVER_CONFIG_SOCKET_ERROR,e).sendToTarget();

        }
    };

    private SipApp.SipObserver sipObserver = new SipApp.SipObserver() {
        @Override
        public void notifyRegState(pjsip_status_code code ,String reason,int expiration) {
            String msg_str = "";
            if (expiration == 0)
                msg_str += "Unregistration";
            else
                msg_str += "Registration";

            if (code.swigValue()/100 == 2) {
                msg_str += " successful";
                handler.obtainMessage(REG_STATE, msg_str).sendToTarget();
            }else {
                msg_str += " failed: " + reason;
                handler.obtainMessage(SERVER_SIP_REG_ERROR,msg_str).sendToTarget();
            }
        }

        @Override
        public void notifyIncomingCall(UIPCall call) {
            handler.obtainMessage(INCOMING_CALL,call).sendToTarget();
        }

        @Override
        public void notifyCallState(UIPCall call) {
            if (currentCall == null || call.getId() != currentCall.getId())
                return;
            CallInfo ci;
            try {
                ci = call.getInfo();
            } catch (Exception e) {
                ci = null;
            }
            Message m = Message.obtain(handler, CALL_STATE, ci);
            m.sendToTarget();

            if (ci != null &&
                    ci.getState() == pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED) {
                currentCall = null;
            }
        }

        @Override
        public void notifyCallMediaState(UIPCall call) {
            Message m = Message.obtain(handler, CALL_MEDIA_STATE, null);
            m.sendToTarget();
        }

        @Override
        public void notifyBuddyState(UIPBuddy buddy) {
            Message m = Message.obtain(handler, BUDDY_STATE, buddy);
            m.sendToTarget();
        }
    };

    @Override
    public boolean handleMessage(Message m) {
        if (listeners.isEmpty()) {
            Log.d(TAG,"you are not set the UIPListener");
            return true;
        }

        if (m.what == 0) {
            sipApp.deinit();
            Runtime.getRuntime().gc();
        } else if (m.what == CALL_STATE) {
            CallInfo ci = (CallInfo) m.obj;
            /* Forward the message to CallActivity */
            for (UIPListener listener : listeners)
                listener.updateCallStatus(ci);
        } else if (m.what == CALL_MEDIA_STATE) {
            //
        } else if (m.what == BUDDY_STATE) {
            throw new UnsupportedOperationException("BUDDY_STATE");
//            UIPBuddy buddy = (UIPBuddy) m.obj;
//            int idx = account.buddyList.indexOf(buddy);
//            /* Update buddy status text, if buddy is valid and
//            * the buddy lists in account and UI are sync-ed.*/
//            if (idx >= 0 && account.buddyList.size() == buddyList.size()) {
//                buddyList.get(idx).put("status", buddy.getStatusText());
//                buddyListAdapter.notifyDataSetChanged();
//                // TODO: selection color/mark is gone after this,
//                //       dont know how to return it back.
//                //buddyListView.setSelection(buddyListSelectedIdx);
//                //buddyListView.performItemClick(buddyListView,
//                //				     buddyListSelectedIdx,
//                //				     buddyListView.
//                //		    getItemIdAtPosition(buddyListSelectedIdx));
//
//                /* Return back Call activity */
//                sipObserver.notifyCallState(currentCall);
//            }
        } else if (m.what == REG_STATE) {
            lastRegStatus = (String) m.obj;
            for(UIPListener listener : listeners)
                listener.updatePhoneRegisterStatus(true,lastRegStatus);
        } else if (m.what == INCOMING_CALL) {
            /* Incoming call */
            final UIPCall call = (UIPCall) m.obj;
            CallOpParam prm = new CallOpParam();
            /* Only one call at anytime */
            if (currentCall != null) {
                /*
                prm.setStatusCode(pjsip_status_code.PJSIP_SC_BUSY_HERE);
                try {
                    call.hangup(prm);
                } catch (Exception e) {}
                */
                // TODO: set status code
                call.delete();
                return true;
            }
            /* Answer with ringing */
            prm.setStatusCode(pjsip_status_code.PJSIP_SC_RINGING);
            try {
                call.answer(prm);
            } catch (Exception e) {}
            currentCall = call;
            for (UIPListener listener : listeners)
                listener.incomingCall(currentCall);
        } else if (m.what == SERVER_CONFIG_FORMAT_ERROR) {
            for (UIPListener listener : listeners)
                listener.onGetUIPConfigError((Throwable) m.obj);
        }else if (m.what == SERVER_CONFIG_SUCCEED) {
            for (UIPListener listener : listeners)
                listener.onGetUIPConfig((UIPConfig) m.obj);
        }else if (m.what == SERVER_CONFIG_ERROR) {
            for (UIPListener listener: listeners)
                listener.onError(SERVER_CONFIG_ERROR,(Throwable) m.obj);
        }else if (m.what == SERVER_CONFIG_SOCKET_ERROR) {
            for (UIPListener listener : listeners)
                listener.onError(SERVER_CONFIG_SOCKET_ERROR, (Throwable) m.obj);
        }else if (m.what == SERVER_MESSAGE) {
            for (UIPListener listener : listeners) {
                UIPMessage message = (UIPMessage) m.obj;
                if (message.id.startsWith("Evt"))
                    listener.onEvent(message);
                else if (message.id.startsWith("Resp")) {
                    listener.onResp(message);
                }
            }
        }else if (m.what == SERVER_SIP_REG_ERROR) {
            for (UIPListener listener : listeners)
                listener.updatePhoneRegisterStatus(false, (String) m.obj);
        }else if (m.what == SERVER_DISCONNECT) {
            // clear the socket client
            isConnected = false;
            for (UIPListener listener : listeners) {
                listener.onDisConnected();
            }
        }else if (m.what == SERVER_CONNECTED) {
            for (UIPListener listener : listeners)
                listener.onConnected();
        }
        else {
            /* Message not handled */
            return false;
        }
        return true;
    }

    public interface UIPListener {
        /**
         *
         * 更新电话状态
         *
         * */
        void updateCallStatus(CallInfo callInfo);

        /**
         *
         * 来电时调用
         * */
        void incomingCall(UIPCall call);

        /**
         * 更新话机注册状态
         * just for log
         * */
        void updatePhoneRegisterStatus(boolean isSuccess,String status);

        void onGetUIPConfig(UIPConfig config);

        void onGetUIPConfigError(Throwable t);

        void onError(int code,Throwable t);

        void onResp(UIPMessage resp);

        void onEvent(UIPMessage event);

        void onConnected();

        void onDisConnected();
    }

    public UIPClient(Context context, UIPUserConfig config) {
        listeners = new ArrayList<>();
        this.context = context;
        this.config = config;
        this.gson = new GsonBuilder()
                .registerTypeAdapter(UIPMessage.class,new UIPMessageAdapter())
                .create();

        configAPI = new Retrofit.Builder()
                .baseUrl(config.getServer())
                .addConverterFactory(GsonConverterFactory.create()).build().create(ConfigAPI.class);
        sipApp.init(sipObserver,context.getFilesDir().getAbsolutePath());
        initAccount();
    }

    public void connect() {

        if (isConnected) {
            Log.d(TAG,"client has called connect()");
            return;
        }
        // guard actions
        isConnected = true;

        Call<UIPConfig> call = configAPI
                .getSIPServer(UIPRequest.getConfigRequest(config.getWorkNo(),config.getAgentDn())
                        .toJson());
        call.enqueue(new Callback<UIPConfig>() {
            @Override
            public void onResponse(Call<UIPConfig> call, Response<UIPConfig> response) {
                if (call.isCanceled()) {
                    disconnect();
                    return;
                }
                if (response.isSuccessful()) {
                    try {
                        httpConfig = response.body();

                        // check is outer ip
                        String ip ;
                        int port;
                        if(TextUtils.isEmpty(httpConfig.OuterAsteriskIP)) {
                            ip = httpConfig.AsteriskIP;
                            port = httpConfig.AsteriskPort;
                        }else {
                            ip = httpConfig.OuterAsteriskIP;
                            port = httpConfig.OuterAsteriskPort;
                        }

                        modifyAccount(
                                "sip:"+config.getAgentDn(),
                                "sip:"+ ip + ":" + port,
                                "",
                                config.getAgentDn(),
                                ""
                        );
                        connectSocket();
                        handler.obtainMessage(SERVER_CONFIG_SUCCEED,httpConfig).sendToTarget();
                    }catch (Exception e) {
                        Log.e(TAG,"UIPConfig format error",e);
                        handler.obtainMessage(SERVER_CONFIG_FORMAT_ERROR,e).sendToTarget();
                        disconnect();
                    }
                }
            }

            @Override
            public void onFailure(Call<UIPConfig> call, Throwable t) {
                Log.e(TAG,"get server config error",t);
                handler.obtainMessage(SERVER_CONFIG_ERROR,t).sendToTarget();
                disconnect();
            }
        });
    }

    private void connectSocket() {
        socketClient =
                new SocketIOClient(
                        httpConfig.WebHttpIP,
                        httpConfig.token,
                        httpConfig.WebHttpPort,
                        socketCallback
                );
        socketClient.setHeartMessage(gson.toJson(UIPMessage.getHeartbeat()));
        socketClient.connect();
    }

    public void disconnect() {
        if (socketClient != null) {
            socketClient.disconnect();
            socketClient = null;
        }
        isConnected = false;
    }

    public void sendMessage(UIPMessage message) {
        socketClient.send(gson.toJson(message));
    }

    private void modifyAccount(String acc_id,String registrar,String proxy,String username,String password ) {
        accCfg.setIdUri(acc_id);
        accCfg.getRegConfig().setRegistrarUri(registrar);
        AuthCredInfoVector creds = accCfg.getSipConfig().getAuthCreds();
        creds.clear();
        if (username.length() != 0) {
            creds.add(
                    new AuthCredInfo("Digest", "*", username, 0, password)
            );
        }
        StringVector proxies = accCfg.getSipConfig().getProxies();
        proxies.clear();
        if (proxy.length() != 0) {
            proxies.add(proxy);
        }
                        /* Enable ICE */
        accCfg.getNatConfig().setIceEnabled(true);
                        /* Finally */
        lastRegStatus = "";
        try {
            account.modify(accCfg);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void initAccount() {
        if (accCfg != null) return;
        accCfg = new AccountConfig();
        accCfg.setIdUri("sip:31031");
        accCfg.getNatConfig().setIceEnabled(true);
        accCfg.getNatConfig().setIceAlwaysUpdate(true);
        //accCfg.getNatConfig().setIceNoRtcp(true);
        //accCfg.getNatConfig().setTurnEnabled(false);
        accCfg.getVideoConfig().setAutoTransmitOutgoing(true);
        accCfg.getVideoConfig().setAutoShowIncoming(true);
        account = sipApp.addAcc(accCfg);
    }

    // not thread safe
    @UiThread
    public void registerUIPListener(UIPListener listener) {
        if (listener == null) return;
            listeners.add(listener);
    }

    // not thread safe
    @UiThread
    public void unregisterUIPListener(UIPListener listener) {
        if (listener == null) return;
            listeners.remove(listener);
    }
}
